using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Collections.Generic;
using Clue_Less_Shared;
using Clue_Less_Shared.Enumerations;
using System.Diagnostics;

namespace Clue_Less_WS
{
    public class GameManager
    {
        private int _gameID;
        private GamePersistance _gp;
        private int _humanPlayerLimit;

        public int HumanPlayerLimit
        {
            get { return _humanPlayerLimit; }
        }

        /// <summary>
        /// Populates deck of cards with cards and then suffles it.
        /// </summary>
        private List<Card> GenerateDeck()
        {
            List<Card> _deck = new List<Card>();

            foreach (string name in Enum.GetNames(typeof(Characters)))
            {
                if (name != "Empty")
                    _deck.Add(new CharacterCard((Characters)Enum.Parse(typeof(Characters), name)));
            }

            foreach (string name in Enum.GetNames(typeof(Weapons)))
            {
                if (name != "Empty")
                    _deck.Add(new WeaponCard((Weapons)Enum.Parse(typeof(Weapons), name)));
            }

            foreach (string name in Enum.GetNames(typeof(Rooms)))
            {
                if (name != "Empty" && name != "Corridor")
                    _deck.Add(new RoomCard((Rooms)Enum.Parse(typeof(Rooms), name)));
            }

            // Shuffle
            Random random = new Random();
            for (int i = 0; i < 100; i++)
            {
                int pos1 = random.Next(0, _deck.Count - 1);
                int pos2 = random.Next(0, _deck.Count - 1);
                //Debug.WriteLine("Num1: " + pos1 + "; Num2: " + pos2);
                Card temp = _deck[pos1];
                _deck[pos1] = _deck[pos2];
                _deck[pos2] = temp;
            }

            return _deck;
        }

        /// <summary>
        /// Takes a shuffled deck of cards and gives out cards to all the players.
        /// </summary>
        /// <param name="players">List of players</param>
        /// <param name="cards">List of cards</param>
        private void GiveOutCards(List<Player> players, List<Card> cards)
        {
            while (cards.Count > 0)
            {
                for (int turn = 0; turn < players.Count; turn++)
                {
                    if (cards.Count == 0)
                        break;

                    Card card = cards[0];
                    players[turn].Cards.Add(card);
                    cards.Remove(card);
                }
            }
        }

        /// <summary>
        /// Returns Envelope. Substracte cards in the envelope from the deck.
        /// </summary>
        /// <param name="deck">Deck of shuffled cards.</param>
        /// <returns>Envelope with first three cards of type Weapon, Character, Room</returns>
        private Envelope GenerateEnvelope(List<Card> deck)
        {
            Envelope env = new Envelope();
            int counter = 0;
            while (env.SuspectCharacter.Value == Characters.Empty ||
                env.SuspectRoom.Value == Rooms.Empty ||
                env.SuspectWeapon.Value == Weapons.Empty)
            {
                if (deck.Count < counter)
                    throw new ClueLessExceptions("Failed to generate an envelope.", null);

                if (deck[counter] is CharacterCard && 
                    env.SuspectCharacter.Value == Characters.Empty)
                {
                    env.SuspectCharacter.Value = ((CharacterCard)deck[counter]).Value;
                    deck.Remove(deck[counter]);
                    continue;
                }

                if (deck[counter] is WeaponCard &&
                    env.SuspectWeapon.Value == Weapons.Empty)
                {
                    env.SuspectWeapon.Value = ((WeaponCard)deck[counter]).Value;
                    deck.Remove(deck[counter]);
                    continue;
                }

                if (deck[counter] is RoomCard &&
                    env.SuspectRoom.Value == Rooms.Empty)
                {
                    env.SuspectRoom.Value = ((RoomCard)deck[counter]).Value;
                    deck.Remove(deck[counter]);
                    continue;
                }

                counter++;
            }

            return env;
        }

        /// <summary>
        /// Starts the game: creates remaining characters, changes game status, sets next player
        /// to the player who logged in first, generates card deck, shuffles them, creates envelope, gives out cards
        /// to players, stored everything to DB.
        /// </summary>
        public void StartGame()
        {
            // Perform startup operations
            _gp.StartGame(_gameID);
            // Shuffling cards
            List<Card> deck = GenerateDeck();
            // Creating and saving enveloped
            Envelope env = GenerateEnvelope(deck);
            _gp.SaveEnvelope(_gameID, env);

            List<Character> characters = _gp.GetCharacters(_gameID, false);
            List<Player> players = new List<Player>();
            foreach (Character character in characters)
            {
                if (character is Player)
                    players.Add((Player)character);
            }
            // Give cards to players
            GiveOutCards(players, deck);
            // Store everything in DB
            foreach (Player player in players)
            {
                _gp.SaveCharacter(_gameID, player);
            }
        }

        /// <summary>
        /// Chacks cards and finishes the game if cards match
        /// </summary>
        /// <param name="weapon"></param>
        /// <param name="room"></param>
        /// <param name="character"></param>
        public bool Accuse(Player player, Weapons weapon, Rooms room, Characters character)
        {
            Envelope env = _gp.GetEnvelope(_gameID);
            if (weapon == env.SuspectWeapon.Value &&
                room == env.SuspectRoom.Value &&
                character == env.SuspectCharacter.Value)
            {
                _gp.FinishGame(_gameID);
                _gp.SendChatMessage(_gameID, -1, player.PlayerName + " has won the game! The crime was committed by " + character + " in " + room + " with " + weapon + ".");
                return true;
            }
            else
            {
                player.Status = PlayerStatus.Disabled;
                _gp.SaveCharacter(_gameID, (Character)player);
                _gp.SendChatMessage(_gameID, -1, player.PlayerName + " has made a wrong guess! The crime was NOT committed by " + character + " in " + room + " with " + weapon + ". The player is OUT.");
                _gp.ChangeTurn(_gameID);
                return false;
            }
        }

        /// <summary>
        /// 1. Checks against envelope. If match, execute Accuse()
        /// 2. Stores suggestion in a DB stack (suggestion DB)
        /// 3. Gets index of next player
        /// 4. Updates suggestion
        /// 5. Sets location of weapon and character with value == character 
        /// to the room the player is in
        /// </summary>
        /// <param name="player"></param>
        /// <param name="weapon"></param>
        /// <param name="room"></param>
        /// <param name="character"></param>
        /// <returns>True is suggestion matches with envelope</returns>
        public bool Suggest(Player player, Weapons weapon, Rooms room, Characters character) 
        {
            Envelope env = _gp.GetEnvelope(_gameID);
            if (weapon == env.SuspectWeapon.Value &&
                room == env.SuspectRoom.Value &&
                character == env.SuspectCharacter.Value)
            {
                return Accuse(player, weapon, room, character);
            }
            else
            {
                _gp.InsertSuggestion(_gameID, player.PlayerID, room, weapon, character);

                Player nextPlayer = getNextPlayer(player, true);
                Suggestion suggestion = _gp.GetSuggestion(_gameID);
                suggestion.DisproverID = nextPlayer.PlayerID;
                _gp.SaveSuggestion(_gameID, suggestion);
                Character ch = getCharacterByType(character);
                ch.XPosition = player.XPosition;
                ch.YPosition = player.YPosition;
                _gp.SaveCharacter(_gameID, ch);
                _gp.SaveWeaponPositions(_gameID, new WeaponPosition(player.XPosition, player.YPosition, weapon));
                return false;
            }
        }

        private Player getNextPlayer(Player player, bool includeDisabled)
        {
            // Current player index
            int currentPlayerIndex = player.Index;
            int nextPlayerIndex = int.MaxValue;
            List<Character> characters = _gp.GetCharacters(_gameID, false);
            foreach (Character character in characters)
            {
                if (!(character is Player))
                    continue;

                if (character.PlayerID == player.PlayerID)
                    continue;

                if (!includeDisabled &&
                    character.Status == PlayerStatus.Disabled)
                    continue;

                if (character.Index > player.Index &&
                    character.Index < nextPlayerIndex)
                {
                    nextPlayerIndex = character.Index;
                }
            }

            if (nextPlayerIndex == int.MaxValue)
            {
                foreach (Character character in characters)
                {
                    if (!(character is Player))
                        continue;

                    if (!includeDisabled &&
                        character.Status == PlayerStatus.Disabled)
                        continue;

                    if (character.Index != player.Index &&
                        character.Index < nextPlayerIndex)
                    {
                        nextPlayerIndex = character.Index;
                    }
                }
            }
            
            if (nextPlayerIndex == int.MaxValue)
            {
                return null;
            }
            else
            {
                foreach (Character character in characters)
                {
                    if (character.Index == nextPlayerIndex)
                        return (Player)character;
                }
            }
            return null;
        }

        private Character getCharacterByType(Characters character)
        {
            List<Character> characters = _gp.GetCharacters(_gameID, true);
            foreach (Character c in characters)
            {
                if (c.CharacterType == character)
                    return c;
            }
            return null;
        }

        /// <summary>
        /// Executed by players trying to disprove suggestion.
        /// If skip is true the player can not disprove the suggestion:
        /// * Sets status of player to Waiting and player with next index to DisprovingSuggestion
        /// If skip is false the card must be non null
        /// * Set is_disproved to True, disproval_card to card, and disproved_by to playerID
        /// * Execute change_turn
        /// </summary>
        /// <param name="player"></param>
        /// <param name="suggestionID"></param>
        /// <param name="skip"></param>
        /// <param name="card"></param>
        public void Disprove(Player player, int suggestionID, bool skip, Card card)
        {
            if (skip)
            {
                Player nextPlayer = getNextPlayer(player, true);
                Suggestion suggestion = _gp.GetSuggestion(_gameID);
                suggestion.DisproverID = nextPlayer.PlayerID;
                _gp.SaveSuggestion(_gameID, suggestion);
            }
            else
            {
                Suggestion suggestion = _gp.GetSuggestion(_gameID);
                suggestion.IsDisproved = true;
                suggestion.DisprovalCard = card;
                _gp.SaveSuggestion(_gameID, suggestion);
                _gp.ChangeTurn(_gameID);
            }
        }

        public GameManager(int GameID, GamePersistance gp)
        {
            _gameID = GameID;
            _gp = gp;
            _humanPlayerLimit = gp.GetHumanPlayerLimit(GameID);
        }
    }
}
